import os
from collections import defaultdict
from operator import itemgetter

import requests

from cpbox.tool import file
from cpbox.tool import template
from cpbox.tool import utils

from cpapp.packer import Packer
from cpapp.version import VersionInfo

def underscore_to_camelcase(value):
    def camelcase():
        yield str.lower
        while True:
            yield str.capitalize

    c = camelcase()
    return ''.join(next(c)(x) if x else '_' for x in value.split("_"))


class ConfigMan():

    def __init__(self, app, prj_root_dir, flavor=None):

        self.app = app
        self.prj_root_dir = prj_root_dir

        raw_data = self.app.read_app_config('app-config.yml')
        if flavor is not None:
            replace_raw_data = self.app.read_app_config('app-config-%s.yml' % (flavor))
            if replace_raw_data is not None:
                self._merge(replace_raw_data, raw_data)

        self.env_fallback_map = raw_data['env_fallback_map']

        self.config_list = defaultdict(dict)
        self.jni_config_list = defaultdict(list)
        self.string_config_list = defaultdict(list)

        string_config = raw_data['string_config']
        for key, item in string_config.items():
            for env, env_fallback in self.env_fallback_map.items():
                if env in item:
                    value = item[env]
                else:
                    value = item[env_fallback]

                self.string_config_list[env].append({'key': 'config_' + key, 'value': value})
                self.config_list[env][key] = value

        # build jni_config
        jni_config = raw_data['jni_config']
        for key, item in jni_config.items():
            camelKey = underscore_to_camelcase(key)
            for env, env_fallback in self.env_fallback_map.items():

                if env in item:
                    value = item[env]
                else:
                    value = item[env_fallback]

                self.jni_config_list[env].append({'key': camelKey, 'value': value})
                self.config_list[env][key] = value

        # sort
        for env, env_fallback in self.env_fallback_map.items():
            self.string_config_list[env] = sorted(self.string_config_list[env], key=itemgetter('key'), reverse=False)
            self.jni_config_list[env] = sorted(self.jni_config_list[env], key=itemgetter('key'), reverse=False)

    def _merge(self, source, destination):
        for key, value in source.items():
            if isinstance(value, dict):
                # get node or create one
                node = destination.setdefault(key, {})
                self._merge(value, node)
            else:
                destination[key] = value

        return destination

    def _real_path(self, path):
        return os.path.realpath(self.prj_root_dir + '/' + path)

    def build_string_config(self, env):
        list = self.string_config_list[env]
        data = {'list': list, 'env': env}
        file = self._real_path('/lib_base/src/main/res/values/config-values.xml')
        template_file = self._real_path('tools/templates/config-string.xml')
        template.render_to_file(template_file, data, file)
        self.app.logger.info('Write config to: ' + file)

    def build_jni_config(self, env):
        list = self.jni_config_list[env]
        is_dev = 'dev' in env or 'sandbox' in env
        data = {'list': list, 'env': env, 'config_list': self.config_list, 'is_dev': is_dev}

        file = self._real_path('/lib_base/src/main/cpp/keys.c')
        template_file = self._real_path('tools/templates/keys.c')
        template.render_to_file(template_file, data, file)
        self.app.logger.info('Write config to: ' + file)

        file = self._real_path('/lib_base/src/main/java/com/xiaoyu/base/AppConfig.java')
        template_file = self._real_path('tools/templates/AppConfig.java')
        template.render_to_file(template_file, data, file)
        self.app.logger.info('Write config to: ' + file)

    def update_doc(self, env):
        url = self.config_list[env]['config_base_url'] + 'api/util/get-config?raw=1'
        # ret = requests.get(url)
        # config_data_file = self._real_path('/app/src/main/assets/init/config.json')
        # file.ensure_dir_for_file(config_data_file)
        # file.file_put_contents(config_data_file, ret.text)


    def enable_push_type(self, push_type, dry_run=True):
        if push_type not in ['xiaomi', 'huawei', 'meizu', 'fcm', 'oppo', 'vivo']:
            self.app.logger.error('this push type is illegal: %s, set to default: xiaomi' % (push_type))
            push_type = 'xiaomi'

        data = {'push_type': push_type}
        template_file = self._real_path('tools/templates/push.gradle')
        file = self._real_path('/app/push.gradle')
        self.app.logger.info('enable_push_type: %s, write config to: %s', push_type, file)
        if not dry_run:
            template.render_to_file(template_file, data, file)

    def try_to_append_local_properties(self, version_info):
        fn = self.prj_root_dir + '/local-autogen.properties'
        utils.dump_properties(version_info.to_properties(), fn)
        self.app.logger.info('update local-autogen.properties: ' + fn)

    def read_version_name(self):
        gradle_properties = utils.load_properties(self.prj_root_dir + '/gradle.properties', to_dict=False)
        version_name_prefix = gradle_properties['VERSION_NAME']

        return version_name_prefix

    def update_project_config(self, build_info_provider, quiet=False, dry_run=False):
        env = build_info_provider.env
        if env not in self.env_fallback_map:
            self.app.logger.error('This env is not existent: %s', env)
            return
        version_info = self._get_version_info(env)
        self.try_to_append_local_properties(version_info)
        self.app.logger.info('build config for env: %s, version_name: %s, version_code=%s', env, version_info.short_version_name, version_info.version_code)
        if dry_run:
            return
        self.build_string_config(env)
        self.build_jni_config(env)
        self.update_doc(env)
        self.prepare_lottie(quiet, dry_run)

    def prepare_lottie(self, quiet=False, dry_run=False):
        packer = Packer(self.app)
        assets_dir = '%s/app/src/main/assets' % (self.prj_root_dir)
        airbnb_loader_dir = assets_dir + '/airbnb_loader'
        images_dir = assets_dir + '/images'
        svga_dir = assets_dir + '/svga'

        file.ensure_dir(airbnb_loader_dir)
        file.ensure_dir(images_dir)
        file.ensure_dir(svga_dir)

        # if not quiet:
        #     packer.prepare_lottie_and_svga(airbnb_loader_dir, images_dir, svga_dir, dry_run)

    def _get_version_info(self, env):
        return self._get_version_info_from_properties(env)

    def _is_invalid_version_info(self, version_info):
        return version_info is None or version_info.short_version_name is None or version_info.version_code is 0

    def _get_version_info_from_draft_new_version(self, build_info_provider):
        return build_info_provider.get_version_info()

    def draft_new_version_locally(self):
        file_path = self.prj_root_dir + '/local-autogen.properties'
        gradle_properties = utils.load_properties(file_path, to_dict=False)
        version_code = int(gradle_properties['VERSION_CODE']) + 1
        gradle_properties['VERSION_CODE'] = str(version_code)
        utils.dump_properties(gradle_properties, file_path)

    def _get_version_info_from_properties(self, env):
        local_autogen_properties = utils.load_properties(self.prj_root_dir + '/local-autogen.properties', to_dict=False)
        gradle_properties = utils.load_properties(self.prj_root_dir + '/gradle.properties', to_dict=False)
        version_name = gradle_properties['VERSION_NAME']
        version_code = local_autogen_properties['VERSION_CODE']
        return VersionInfo(env, version_name, version_code)

